package br.ufpi.hu.prescricao.dao;

import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Order;
import javax.persistence.criteria.Root;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.Metamodel;

public abstract class GenericDAO<T> {
	private final static String UNIT_NAME = "lrsfollow";

	@PersistenceContext(unitName = UNIT_NAME)
	public EntityManager em;

	public Class<T> entityClass;

	public GenericDAO(Class<T> entityClass) {
		this.entityClass = entityClass;
	}

	public void save(T entity) {
		em.persist(entity);
	}
	
	public void delete(T entity) {
		T entityToBeRemoved = em.merge(entity);

		em.remove(entityToBeRemoved);
	}

	public T update(T entity) {
		return em.merge(entity);
	}

	public T find(int entityID) {
		return em.find(entityClass, entityID);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> findAll(Map<String, String> orderByParams, Map<String, Object> restriction) { //nome do campo, ordem
		CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
		Root root = cq.from(entityClass);
		cq.select(root);

		Metamodel m = em.getMetamodel();
		EntityType<T> sol = m.entity(getGenericClass());
		
		List<Order> orderList = new ArrayList<Order>();

		for (String param : orderByParams.values()) {

			String key = getKeyByValue(orderByParams,param);
			Order order;

			if (param.equals("asc")) {
				order = em.getCriteriaBuilder().asc(root.get(sol.getSingularAttribute(key)));				
			}
			else if (param.equals("desc")) {
				order = em.getCriteriaBuilder().desc(root.get(sol.getSingularAttribute(key)));
			}
			else {
				//erro!
				throw new RuntimeException();
			}
			
			orderList.add(order);
		}
		
		if (orderList.size() != 0) {
			cq.orderBy(orderList);
		}
		
		if (restriction!=null) {
			
			for (String key : restriction.keySet()) {
				
				Expression expression = em.getCriteriaBuilder().equal(root.get(key), restriction.get(key));
				
				cq.where(expression);	
			}
		}
		
		return em.createQuery(cq).getResultList();
	}

	public static <T, E> T getKeyByValue(Map<T, E> map, E value) {
		for (Entry<T, E> entry : map.entrySet()) {
			if (value.equals(entry.getValue())) {
				return entry.getKey();
			}
		}
		return null;
	}

	@SuppressWarnings("rawtypes")
	private Class getGenericClass() {
		ParameterizedType parameterizedType = (ParameterizedType)getClass()
				.getGenericSuperclass();
		return (Class) parameterizedType.getActualTypeArguments()[0];
	}

	// Using the unchecked because JPA does not have a
	// em.getCriteriaBuilder().createQuery()<T> method
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public List<T> findAll() {
		CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
		Root root = cq.from(entityClass);
		cq.select(root);
		return em.createQuery(cq).getResultList();
	}

	// Using the unchecked because JPA does not have a
	// ery.getSingleResult()<T> method
	@SuppressWarnings("unchecked")
	protected T findOneResult(String namedQuery, Map<String, Object> parameters) {
		T result = null;

		try {
			Query query = em.createNamedQuery(namedQuery);

			// Method that will populate parameters if they are passed not null and empty
			if (parameters != null && !parameters.isEmpty()) {
				populateQueryParameters(query, parameters);
			}

			result = (T) query.getSingleResult();

		} catch (Exception e) {
			System.out.println("Error while running query: " + e.getMessage());
			e.printStackTrace();
		}

		return result;
	}

	private void populateQueryParameters(Query query, Map<String, Object> parameters) {

		for (Entry<String, Object> entry : parameters.entrySet()) {
			query.setParameter(entry.getKey(), entry.getValue());
		}
	}
}
